﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Tilemaps;
using Random = UnityEngine.Random;

namespace Map
{
    public class MapGenerator : MonoBehaviour
    {
        public static MapInfo Map;
        
        public Tilemap tilemap;
        public TileBase tile;
        public TileBase dest;

        [SerializeField] private int[] initialResources = { 30, 10 };
        [SerializeField] private int[] resourceConsumes = { 3, 1 };
        [SerializeField] private float[] resourceConsumePerWalkAtDifficulty = { 0.25f, 0.5f, 0.75f };
        [SerializeField] private float[] numPathsAtDifficulty = { 10, 6, 4 };
        [SerializeField] private int[] resourcePerItem = { 20, 6 };

        [SerializeField] private Vector2Int startPosition = new(0, -1);
        [SerializeField] private int destinationY = -70;
        [SerializeField] private int xBoundLeft = -10;
        [SerializeField] private int xBoundRight = 10;
        
        [SerializeField] private int mapWidth = 21;
        [SerializeField] private int mapHeight = 70;
        [SerializeField] private int rootXFromLeft = 11;

        private readonly List<Vector2Int> _walkDirections = new();
        

        public void Generate(int seed, int difficulty)
        {
            Random.InitState(seed);
            Map = new MapInfo(mapWidth, mapHeight, rootXFromLeft);
            
            Vector2Int destination = PickDestination();
            Map.Destination = destination;
            // tilemap.SetTile(new Vector3Int(destination.x, destination.y), dest);

            for (int i = 0; i < numPathsAtDifficulty[difficulty]; i++)
            {
                int[] hasResources = new int[initialResources.Length];
                Array.Copy(initialResources, hasResources, initialResources.Length);
                
                Vector2Int currentPosition = startPosition;
                
                // If <= startConvergeY, move towards the destination
                int startConvergeY = Random.Range((int)(destinationY * 0.5f), destinationY);

                int loops = 0;
                while (currentPosition.y > destinationY)
                {
                    currentPosition = GenerateRandomWalk(currentPosition, destination, startConvergeY, difficulty, hasResources);
                    
                    loops++;
                    if (loops >= 1000)
                    {
                        Debug.LogError("Too many walks");
                        break;
                    }
                }
                
            }

            Map.GenerateObstacles();
        }

        public Vector2Int GenerateRandomWalk(
            Vector2Int currentStartPosition, 
            Vector2Int destination, 
            int startConvergeY,
            int difficulty, 
            int[] hasResources)
        {
            int maxDistance = int.MaxValue;
            int lackingResource = 0;
            for (int i = 0; i < hasResources.Length; i++)
            {
                int supportDistance = hasResources[i] / resourceConsumes[i];
                if (supportDistance < maxDistance)
                {
                    maxDistance = supportDistance;
                    lackingResource = i;
                }
            }
            // Debug.Log($"Max path length: {maxDistance}");
            
            if (maxDistance == 0)
            {
                return currentStartPosition;
            }

            maxDistance = (int)(maxDistance * resourceConsumePerWalkAtDifficulty[difficulty]);
            // Debug.Log($"Max path length at current difficulty: {maxDistance}");
            
            int steps = Random.Range(1, maxDistance + 1);
            // Debug.Log($"Sampling path with length {steps}...");
            Vector2Int currentPosition = currentStartPosition;
            for (int i = 0; i < steps; i++)
            {
                Vector2Int step;
                if (currentPosition.y <= startConvergeY)
                {
                    step = PickDirectionTowardsDestination(currentPosition, destination);
                }
                else
                {
                    step = PickDirection(currentPosition);
                }
                currentPosition += step;
                
                if (currentPosition == destination)
                {
                    return currentPosition;
                }
                
                if (Map.TryGetResourceID(currentPosition, out int resourceID))
                {
                    AddResource(resourceID, hasResources);
                }
                else
                {
                    Map.PutPath(currentPosition);
                }
                // tilemap.SetTile(new Vector3Int(currentPosition.x, currentPosition.y), tile);
            }
            
            if (!Map.TryGetResourceID(currentPosition, out int _))
            {
                Map.PutResource(currentPosition, lackingResource);
                AddResource(lackingResource, hasResources);
            }
            
            return currentPosition;
        }

        private Vector2Int PickDirection(Vector2Int currentPosition)
        {
            _walkDirections.Clear();

            if (currentPosition.x > xBoundLeft)
            {
                _walkDirections.Add(Vector2Int.left);
            }
            if (currentPosition.x < xBoundRight)
            {
                _walkDirections.Add(Vector2Int.right);
            }

            if (currentPosition.y > destinationY)
            {
                // 2x probability
                _walkDirections.Add(Vector2Int.down);
                _walkDirections.Add(Vector2Int.down);
            }
            
            return _walkDirections[Random.Range(0, _walkDirections.Count)];
        }
        
        private Vector2Int PickDirectionTowardsDestination(Vector2Int currentPosition, Vector2Int destination)
        {
            _walkDirections.Clear();

            if (currentPosition.y > destination.y)
            {
                _walkDirections.Add(Vector2Int.down);
            }

            if (currentPosition.x < destination.x)
            {
                _walkDirections.Add(Vector2Int.right);
            }
            else if (currentPosition.x > destination.x)
            {
                _walkDirections.Add(Vector2Int.left);
            }
            
            return _walkDirections[Random.Range(0, _walkDirections.Count)];
        }

        private Vector2Int PickDestination()
        {
            const float destinationRangeFactor = 0.5f;
            return new Vector2Int((int)(Random.Range(xBoundLeft, xBoundRight) * destinationRangeFactor), destinationY);
        }

        private void AddResource(int resourceID, int[] hasResources)
        {
            hasResources[resourceID] += resourcePerItem[resourceID];
        }
    }
}